import Geom from '../geom'
import utils from '../utils'
import paths from '../paths'
import common from '../common.js'
import polygons from '../polygons'
import Dataset from '../dataset'
import {DataTable} from '../data'
import { ArcCollection } from './index.js'

function cleanPathsAfterImport(dataset, opts) {
  const arcs = dataset.arcs
  let snapDist
  if (opts.snap || opts.auto_snap || opts.snap_interval) { // auto_snap is older name
    if (opts.snap_interval) {
      snapDist = Geom.convertIntervalParam(opts.snap_interval, internal.getDatasetCRS(dataset))
    }
    if (arcs) {
      paths.snapCoords(arcs, snapDist)
    }
  }
  dataset.layers.forEach(function(lyr) {
    if (common.layerHasPaths(lyr)) {
      polygons.cleanShapes(lyr.shapes, arcs, lyr.geometry_type)
    }
  })
}
/**
 * Accumulates points in buffers until #endPath() is called
 * @param {*} drain callback: function(xarr, yarr, size) {}
 */
function PathImportStream(drain) {
  let buflen = 10000
  let xx = new Float64Array(buflen)
  let yy = new Float64Array(buflen)
  let i = 0

  this.endPath = function() {
    drain(xx, yy, i)
    i = 0
  }

  this.addPoint = function(x, y) {
    if (i >= buflen) {
      buflen = Math.ceil(buflen * 1.3)
      xx = utils.extendBuffer(xx, buflen)
      yy = utils.extendBuffer(yy, buflen)
    }
    xx[i] = x
    yy[i] = y
    i++
  }
}
/**
 * Import path data from a non-topological source (Shapefile, GeoJSON, etc)
 * in preparation for identifying topology.
 * @param {*} opts .reserved_points -- estimate of points in dataset, for pre-allocating buffers
 */
function PathImporter(opts) {
  const bufSize = opts.reserved_points > 0 ? opts.reserved_points : 20000
  let xx = new Float64Array(bufSize)
  let yy = new Float64Array(bufSize)
  const shapes = []
  const properties = []
  const nn = []
  const types = []
  let collectionType = opts.type || null
  let round = null
  let pathId = -1
  let shapeId = -1
  let pointId = 0
  let dupeCount = 0
  let openRingCount = 0
  
  if (opts.precision) {
    round = Geom.getRoundingFunction(opts.precision)
  }

  utils.extend(this, new PathImportStream(importPathCoords))

  this.startShape = function(d) {
    shapes[++shapeId] = null
    if (d) properties[shapeId] = d
  }
  this.importLine = function(points) {
    if (points.length < 2) {
      // verbose("Skipping a defective line")
      return
    }
    setShapeType('polyline')
    this.importPath(points)
  }
  this.importPoints = function(points) {
    setShapeType('point')
    if (round) {
      points.forEach(function(p) {
        p[0] = round(p[0])
        p[1] = round(p[1])
      })
    }
    points.forEach(appendToShape)
  }
  this.importRing = function(points, isHole) {
    const area = Geom.getPlanarPathArea2(points)
    if (!area || points.length < 4) {
      // verbose("Skipping a defective ring")
      return
    }
    setShapeType('polygon')
    if (isHole === true && area > 0 || isHole === false && area < 0) {
      // verbose("Warning: reversing", isHole ? "a CW hole" : "a CCW ring")
      points.reverse()
    }
    this.importPath(points)
  }
  // Import an array of [x, y] Points
  this.importPath = function importPath(points) {
    let p = null
    for (let i = 0, n = points.length; i < n; i += 1) {
      p = points[i]
      this.addPoint(p[0], p[1])
    }
    this.endPath()
  }
  // Return imported dataset
  // Apply any requested snapping and rounding
  // Remove duplicate points, check for ring inversions
  //
  this.done = function() {
    let arcs = null
    let layers = null
    let lyr = {name: ''}

    if (dupeCount > 0) {
      // verbose(utils.format("Removed %,d duplicate point%s", dupeCount, utils.pluralSuffix(dupeCount)))
    }
    if (openRingCount > 0) {
      // message(utils.format("Closed %,d open polygon ring%s", openRingCount, utils.pluralSuffix(openRingCount)))
    }
    if (pointId > 0) {
       if (pointId < xx.length) {
        xx = xx.subarray(0, pointId)
        yy = yy.subarray(0, pointId)
      }
      arcs = new ArcCollection(nn, xx, yy)

      //if (opts.snap || opts.auto_snap || opts.snap_interval) { // auto_snap is older name
      //  internal.snapCoords(arcs, opts.snap_interval);
      //}
    }

    if (collectionType == 'mixed') {
      layers = Dataset.divideFeaturesByType(shapes, properties, types)
    } else {
      lyr = {geometry_type: collectionType}
      if (collectionType) {
        lyr.shapes = shapes
      }
      if (properties.length > 0) {
        lyr.data = new DataTable(properties)
      }
      layers = [lyr]
    }

    layers.forEach((lyr) => {
      if (lyr.data) {
        utils.fixInconsistentFields(lyr.data.getRecords())
      }
    })

    return {
      arcs: arcs || null,
      info: {},
      layers: layers
    }
  }
  function setShapeType(t) {
    const currType = shapeId < types.length ? types[shapeId] : null
    if (!currType) {
      types[shapeId] = t
      if (!collectionType) {
        collectionType = t
      } else if (t != collectionType) {
        collectionType = 'mixed'
      }
    } else if (currType != t) {
      // stop("Unable to import mixed-geometry GeoJSON features")
      utils.error('Unable to import mixed-geometry GeoJSON features')
    }
  }
  function checkBuffers(needed) {
    if (needed > xx.length) {
      const newLen = Math.max(needed, Math.ceil(xx.length * 1.5))
      xx = utils.extendBuffer(xx, newLen, pointId)
      yy = utils.extendBuffer(yy, newLen, pointId)
    }
  }
  function appendToShape(part) {
    const currShape = shapes[shapeId] || (shapes[shapeId] = [])
    currShape.push(part)
  }
  function appendPath(n) {
    pathId++
    nn[pathId] = n
    appendToShape([pathId])
  }
  function importPathCoords(xsrc, ysrc, n) {
    let count = 0
    let x = null
    let y = null
    let prevX = null
    let prevY = null
    checkBuffers(pointId + n)
    for (let i=0; i<n; i++) {
      x = xsrc[i]
      y = ysrc[i]
      if (round) {
        x = round(x)
        y = round(y)
      }
      if (i > 0 && x == prevX && y == prevY) {
        dupeCount++
      } else {
        xx[pointId] = x
        yy[pointId] = y
        pointId++
        count++
      }
      prevY = y
      prevX = x
    }

    // check for open rings
    if (collectionType == 'polygon' && count > 0) {
      if (xsrc[0] != xsrc[n-1] || ysrc[0] != ysrc[n-1]) {
        checkBuffers(pointId + 1)
        xx[pointId] = xsrc[0]
        yy[pointId] = ysrc[0]
        openRingCount++
        pointId++
        count++
      }
    }

    appendPath(count)
  }
}
export {
  PathImporter,
}
export default {
  cleanPathsAfterImport,
}